pub use plotters::style::{colors::*, Color};
use plotters::{
    coord::{ranged3d::Cartesian3d, types::RangedCoordf64, Shift},
    prelude::*,
};
use std::ops::Range;

/// 绘画区域
pub struct DrawArea<'a>(DrawingArea<BitMapBackend<'a>, Shift>);

impl<'a> DrawArea<'a> {
    /// 新建一个绘图区域
    /// save_image_path: 保存的图片的路径和文件名
    /// size: 图片的大小
    /// back_ground: 背景颜色
    pub fn new_draw_area(save_image_path: &'a str, size: (u32, u32)) -> anyhow::Result<Self> {
        let backend = BitMapBackend::new(save_image_path, size).into_drawing_area();
        backend.fill(&WHITE)?;
        Ok(Self(backend))
    }

    /// 填充背景颜色
    pub fn fill_background(self, background: RGBAColor) -> anyhow::Result<Self> {
        self.0.fill(&background)?;
        Ok(self)
    }

    /// 水平分割绘图区域
    /// percent: 指定左侧区域的占比(0-100)
    pub fn split_horizontally(self, percent: u32) -> (Self, Self) {
        let (left, right) = self.0.split_horizontally(percent.percent_width());
        (Self(left), Self(right))
    }

    /// 垂直分割绘图区域
    /// percent: 指定上面区域的占比(0-100)
    pub fn split_vertically(self, percent: u32) -> (Self, Self) {
        let (upper, lower) = self.0.split_vertically(percent.percent_height());
        (Self(upper), Self(lower))
    }

    /// 通过指定几行几列平均的划分区域
    pub fn split_evenly(self, (row, col): (usize, usize)) -> Vec<Self> {
        let ret = self.0.split_evenly((row, col));
        ret.into_iter().map(Self).collect()
    }

    /// 生成具有2D坐标的图标显示内容框架
    pub fn generate_chart_context2d(
        self,
        title: &str,
        x: Range<f64>,
        y: Range<f64>,
    ) -> anyhow::Result<DrawContext2D<'a>> {
        let mut chart = ChartBuilder::on(&self.0)
            .caption(title, ("sans-serif", 30))
            .set_label_area_size(LabelAreaPosition::Left, 40)
            .set_label_area_size(LabelAreaPosition::Bottom, 40)
            .margin(20)
            .build_cartesian_2d(x, y)?;
        chart.configure_mesh().draw()?;
        Ok(DrawContext2D {
            draw_area: self.0,
            chart_context: chart,
        })
    }

    /// 生成具有3D坐标的图标显示内容框架
    pub fn generate_chart_context3d(
        self,
        title: &str,
        x: Range<f64>,
        y: Range<f64>,
        z: Range<f64>,
        show_axes: bool,
    ) -> anyhow::Result<DrawContext3D<'a>> {
        let mut chart = ChartBuilder::on(&self.0)
            .margin(20)
            .caption(title, ("sans-serif", 30))
            .build_cartesian_3d(x, y, z)?;

        if show_axes {
            chart.configure_axes().draw()?;
        }

        Ok(DrawContext3D {
            draw_area: self.0,
            chart_context: chart,
        })
    }
}

/// 绘画2D图片的内容
pub struct DrawContext2D<'a> {
    #[allow(dead_code)]
    draw_area: DrawingArea<BitMapBackend<'a>, Shift>,
    chart_context: ChartContext<'a, BitMapBackend<'a>, Cartesian2d<RangedCoordf64, RangedCoordf64>>,
}

impl DrawContext2D<'_> {
    /// 画线
    pub fn draw_2d_line(
        mut self,
        points: impl Iterator<Item = (f64, f64)>,
        color: RGBAColor,
        label: &str,
    ) -> anyhow::Result<Self> {
        self.chart_context
            .draw_series(LineSeries::new(points, color))?
            .label(label)
            .legend(move |(x, y)| Rectangle::new([(x, y), (x + 20, y + 2)], color.filled()));
        Ok(self)
    }

    /// 画点
    pub fn draw_2d_dot(
        mut self,
        points: impl Iterator<Item = (f64, f64)>,
        color: RGBAColor,
        size: u32,
        label: &str,
    ) -> anyhow::Result<Self> {
        self.chart_context
            .draw_series(points.map(|(x, y)| Circle::new((x, y), size, color.filled())))?
            .label(label)
            .legend(move |(x, y)| Circle::new((x + 10, y), size, color.filled()));
        Ok(self)
    }

    /// 绘画面积图
    pub fn draw_area(
        mut self,
        x: Range<isize>,
        y: &[f64],
        fill: RGBAColor,
        border: RGBAColor,
        label: &str,
    ) -> anyhow::Result<Self> {
        self.chart_context
            .draw_series(
                AreaSeries::new(
                    x.into_iter().map(|x| x as f64).zip(y.iter().copied()),
                    0.0,
                    fill,
                )
                .border_style(border),
            )?
            .label(label)
            .legend(move |(x, y)| Rectangle::new([(x, y), (x + 20, y + 5)], fill.filled()));
        Ok(self)
    }

    /// 竖立的直方图
    pub fn draw_histogram_vertical(
        mut self,
        data: impl Iterator<Item = (f64, f64)>,
        fill: RGBAColor,
        label: &str,
    ) -> anyhow::Result<Self> {
        self.chart_context
            .draw_series(data.map(|(x, y)| {
                let mut bar = Rectangle::new([(x - 0.5, 0.0), (x + 0.5, y)], fill.filled());
                bar.set_margin(0, 0, 5, 5);
                bar
            }))?
            .label(label)
            .legend(move |(x, y)| Rectangle::new([(x, y), (x + 5, y + 5)], fill.filled()));
        Ok(self)
    }

    /// 横向的直方图
    pub fn draw_histogram_horizon(
        mut self,
        data: impl Iterator<Item = (f64, f64)>,
        fill: RGBAColor,
        label: &str,
    ) -> anyhow::Result<Self> {
        self.chart_context
            .draw_series(data.map(|(x, y)| {
                let mut bar = Rectangle::new([(0.0, y - 0.5), (x, y + 0.5)], fill.filled());
                bar.set_margin(5, 5, 0, 0);
                bar
            }))?
            .label(label)
            .legend(move |(x, y)| Rectangle::new([(x, y), (x + 20, y + 5)], fill.filled()));
        Ok(self)
    }

    /// 绘画图例说明
    pub fn draw_legend(
        mut self,
        back_ground: RGBAColor,
        border: RGBAColor,
    ) -> anyhow::Result<Self> {
        self.chart_context
            .configure_series_labels()
            .background_style(back_ground.mix(0.8))
            .border_style(border)
            .position(SeriesLabelPosition::UpperRight)
            .label_font(("Monoca", 16))
            .draw()?;
        Ok(self)
    }
}

/// 绘制3D图片Context
pub struct DrawContext3D<'a> {
    #[allow(dead_code)]
    draw_area: DrawingArea<BitMapBackend<'a>, Shift>,
    chart_context: ChartContext<
        'a,
        BitMapBackend<'a>,
        Cartesian3d<RangedCoordf64, RangedCoordf64, RangedCoordf64>,
    >,
}

impl DrawContext3D<'_> {
    /// 调整透视
    pub fn perspective(mut self, pitch: f64, yaw: f64, scale: f64) -> Self {
        self.chart_context.with_projection(|mut pb| {
            pb.pitch = pitch;
            pb.yaw = yaw;
            pb.scale = scale;
            pb.into_matrix()
        });
        self
    }

    /// 绘制3D线
    pub fn draw_line(
        &mut self,
        data: impl Iterator<Item = (f64, f64, f64)>,
        color: RGBAColor,
    ) -> anyhow::Result<()> {
        self.chart_context
            .draw_series(LineSeries::new(data, color))?;
        Ok(())
    }

    /// 绘制3D的面
    pub fn draw_surface<F: Fn(f64, f64) -> f64>(
        &mut self,
        x: impl Iterator<Item = f64>,
        y: impl Iterator<Item = f64>,
        f: F,
        color: RGBAColor,
    ) -> anyhow::Result<()> {
        self.chart_context
            .draw_series(SurfaceSeries::xoz(x, y, f).style(color))?;
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use plotters::style::Color;

    use crate::DrawArea;

    #[test]
    fn test_show_image() -> anyhow::Result<()> {
        let path = String::from("./multi_area.png");
        let area = DrawArea::new_draw_area(&path, (1024, 600))?;
        let ares = area.split_evenly((2, 2));
        for (idx, a) in ares.into_iter().enumerate() {
            if idx == 0 {
                a.generate_chart_context2d("Line1", 0.0..10.0, 0.0..10.0)?
                    .draw_2d_line(
                        [(1.0, 1.0), (2.0, 2.0)].into_iter(),
                        crate::RED.mix(1.0),
                        "Line",
                    )?
                    .draw_2d_dot(
                        [(5.0, 6.0), (7.8, 9.3)].into_iter(),
                        crate::BLUE.mix(1.0),
                        3,
                        "Circle",
                    )?
                    .draw_legend(crate::MAGENTA.mix(0.8), crate::YELLOW.mix(1.0))?;
            } else if idx == 1 {
                a.generate_chart_context2d("Line2", 0.0..10.0, 0.0..10.0)?
                    .draw_histogram_horizon(
                        [(1.0, 3.0), (3.0, 7.0), (5.0, 8.0)].into_iter(),
                        crate::BLUE.mix(0.7),
                        "histogram_horizon",
                    )?
                    .draw_legend(crate::WHITE.mix(0.8), crate::BLACK.mix(0.9))?;
            } else if idx == 2 {
                a.generate_chart_context2d("Area", 0.0..10.0, 0.0..10.0)?
                    .draw_area(
                        0..3,
                        &[3.4, 5.6, 7.8],
                        crate::BLUE.mix(1.0),
                        crate::RED.mix(0.8),
                        "area",
                    )?
                    .draw_legend(crate::RED.mix(0.6), crate::YELLOW.mix(1.0))?;
            } else {
                a.generate_chart_context2d("histogram_vertical", 0.0..10.0, 0.0..10.0)?
                    .draw_histogram_vertical(
                        [(1.0, 3.0), (3.0, 7.0), (5.0, 8.0)].into_iter(),
                        crate::BLUE.mix(0.7),
                        "histogram_vertical",
                    )?
                    .draw_legend(crate::WHITE.mix(0.8), crate::BLACK.mix(0.9))?;
            }
        }
        Ok(())
    }

    #[test]
    fn test_show_image_3d() -> anyhow::Result<()> {
        let area = DrawArea::new_draw_area("./image3d.png", (640, 480))?;
        let (left, right) = area.split_horizontally(50);

        let mut chart_context_left = left
            .generate_chart_context3d("3D Example", -1.0..1.0, -1.0..1.0, -1.0..1.0, true)?
            .perspective(1.2, 0.2, 0.8);
        let data = (-100..100)
            .map(|x| x as f64 / 100.0)
            .map(|y| ((y * 10.0).sin(), y, (y * 10.0).cos()));
        chart_context_left.draw_line(data, crate::RED.mix(1.))?;

        let mut chart_context_right = right
            .generate_chart_context3d("3D Surface", -3.0..3.0, -3.0..3.0, -3.0..3.0, false)?
            .perspective(0.8, 0.5, 0.8);
        chart_context_right.draw_surface(
            (-25..25).map(|x| x as f64 / 10.0),
            (-25..25).map(|x| x as f64 / 10.0),
            |x, y| (x * x + y * y).cos(),
            crate::BLUE.mix(0.2),
        )?;

        Ok(())
    }
}
